package ru.spb.osll.myfit.client.widget;

import ca.nanometrics.gflot.client.*;
import ca.nanometrics.gflot.client.event.PlotSelectedListener;
import ca.nanometrics.gflot.client.event.PlotSelectingListener;
import ca.nanometrics.gflot.client.event.PlotUnselectedListener;
import ca.nanometrics.gflot.client.options.*;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.event.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.i18n.client.HasDirection;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import ru.spb.osll.myfit.client.dialog.ErrorDialog;
import ru.spb.osll.myfit.client.service.ChartComparisonServiceAsync;
import ru.spb.osll.myfit.common.ComparisonResult;
import ru.spb.osll.myfit.common.Range;
import ru.spb.osll.myfit.common.chart.ChartCurve;
import ru.spb.osll.myfit.common.chart.ChartData;
import ru.spb.osll.myfit.common.chart.ChartPoint;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class ChartComparator extends Composite {
  private final int DEFAULT_CHART_WIDTH = 600;
  private final int DEFAULT_HEIGHT = 300;
  private final int DEFAULT_PREVIEW_HEIGHT = 100;
  private final int DEFAULT_CONTENT_GAP = 8;
  private ChartComparisonServiceAsync m_chartComparisonService;
  private List<String> m_ids;
  private final ErrorDialog m_errorDialog = new ErrorDialog();

  public ChartComparator(List<String> ids, List<ChartData> chartDataList, ChartComparisonServiceAsync chartsServiceAsync) {
    PlotModel plotModel = createPlotModel(chartDataList);

    m_ids = ids;
    m_chartComparisonService = chartsServiceAsync;

    initWidget(new ChartStackPanel(plotModel));
  }

  private PlotOptions createCommonPlotOptions() {
    PlotOptions selectionPlotOptions = new PlotOptions();
    selectionPlotOptions.setLegendOptions(new LegendOptions().setShow(false))
            .setGlobalSeriesOptions(
                    new GlobalSeriesOptions().setLineSeriesOptions(new LineSeriesOptions().setLineWidth(1).setFill(false)).setShadowSize(0d));
    return selectionPlotOptions;
  }

  private SimplePlot createSimplePlot(PlotModel plotModel) {
    PlotOptions simplePlotOptions = createCommonPlotOptions();
    simplePlotOptions.addYAxisOptions(new AxisOptions().setMinimum(0));
    SimplePlot simplePlot = new SimplePlot(plotModel, simplePlotOptions);
    resizePlot(simplePlot);
    return simplePlot;
  }

  private void resizePlot(SimplePlot simplePlot) {
    simplePlot.setWidth(DEFAULT_CHART_WIDTH);
    simplePlot.setHeight(DEFAULT_HEIGHT);
  }

  private void resizePreviewPlot(PassiveSelectionPlot passiveSelectionPlot) {
    passiveSelectionPlot.setWidth(DEFAULT_CHART_WIDTH);
    passiveSelectionPlot.setHeight(DEFAULT_PREVIEW_HEIGHT);
  }

  private PlotModel createPlotModel(List<ChartData> chartRenderDataList) {
    PlotModel plotModel = new PlotModel(PlotModelStrategy.defaultStrategy());

    for (ChartData chartData : chartRenderDataList) {
      //todo: rewrite next line with appropriate code
      ChartCurve curve = chartData.getCurves().get(0);
      SeriesHandler seriesHandler = plotModel.addSeries(chartData.getName(), curve.getHexColorCode());
      for (ChartPoint chartPoint :  curve.getPoints()) {
        seriesHandler.add(new DataPoint(chartPoint.getX(), chartPoint.getY()));
      }
    }

    return plotModel;
  }

  private class ChartStackPanel extends Composite {
    private final StackLayoutPanel m_widget = new StackLayoutPanel(Style.Unit.PX);
    private PlotModel m_plotModel;
    private int m_regionsNumber = 0;

    public ChartStackPanel(PlotModel plotModel) {
      VerticalPanel panel = new VerticalPanel();

      m_widget.setWidth(Integer.toString(DEFAULT_CHART_WIDTH));

      m_widget.addSelectionHandler(createSelectionHandler());
      m_widget.addBeforeSelectionHandler(createBeforeSelectionHandler());

      m_plotModel = plotModel;

      addPlot();

      panel.add(m_widget);

      panel.add(new RegionSelectorAdder());

      initWidget(panel);
    }

    private BeforeSelectionHandler<Integer> createBeforeSelectionHandler() {
      return new BeforeSelectionHandler<Integer>() {
        @Override
        public void onBeforeSelection(BeforeSelectionEvent<Integer> integerBeforeSelectionEvent) {
          int i = m_widget.getVisibleIndex();
          if (i >= 0 && i < m_widget.getWidgetCount()) {
            Widget header = m_widget.getHeaderWidget(i);
            Widget content = ((VerticalPanel)m_widget.getWidget(i)).getWidget(1);
            if (header instanceof PassiveSelectionPlot) {
              PassiveSelectionPlot plot = (PassiveSelectionPlot)header;
              plot.getOverlay().setStyleName(IStyleConstants.SMOKING_OVERLAY_FOR_CHART);
              hideOverflow(content);
            }
          }
        }
      };
    }

    private SelectionHandler<Integer> createSelectionHandler() {
      return new SelectionHandler<Integer>() {
        @Override
        public void onSelection(SelectionEvent<Integer> integerSelectionEvent) {
          int i = integerSelectionEvent.getSelectedItem();
          Widget header = m_widget.getHeaderWidget(i);
//          LayoutPanel layoutPanel = (LayoutPanel)header.getParent().getParent();
//          element.setAttribute("asdf", "asdf");
//          element.setPropertyString("visibility", "hidden");
//          element.setPropertyInt("top", element.getAbsoluteTop() - DEFAULT_PREVIEW_HEIGHT);
//          DOM.setIntStyleAttribute(element, "top", DOM.getIntStyleAttribute(element, "top") + DEFAULT_PREVIEW_HEIGHT);
//          DOM.setIntStyleAttribute(element, "top", "300px");
//          DOM.setStyleAttribute(element, "marginLeft", "300px");
//          layoutPanel.setWidgetTopHeight(header.getParent(), 200, Style.Unit.PX, 200, Style.Unit.PX);
          Widget content = ((VerticalPanel)m_widget.getWidget(i)).getWidget(1);
          if (header instanceof PassiveSelectionPlot) {
            PassiveSelectionPlot plot = (PassiveSelectionPlot)header;
            plot.getOverlay().setStyleName(IStyleConstants.EMPTY_DIV_OVERLAY_FOR_CHART_PREVIEW);
            showOverflow(content);
          }
        }
      };
    }

    public void addPlot() {
      ++m_regionsNumber;

      VerticalPanel selectionPanel = new VerticalPanel();
      VerticalPanel gap = new VerticalPanel();
      gap.setHeight(Integer.toString(DEFAULT_CONTENT_GAP));
      selectionPanel.add(gap);

      PassiveSelectionPlot overviewPlot = createPassiveSelectionPlot();

      OptionsSelector optionsSelector = new OptionsSelector();
      SimplePlot selectionPlot = createSelectionPlot(overviewPlot, optionsSelector);
//      selectionPlot.addDomHandler(new ContextMenuHandler() {
//        @Override
//        public void onContextMenu(final ContextMenuEvent event) {
//          event.preventDefault();
//          event.stopPropagation();
//
//          SimplePlot oldPlot = (SimplePlot)event.getSource();
////          SimplePlot newPlot = new SimplePlot(m_plotModel, oldPlot.getPlotOptions().setGridOptions(new GridOptions().setCanvasText(new CanvasTextOptions().setShow(true))));
////          newPlot.
//          oldPlot.saveAsImage();
//        }
//      }, ContextMenuEvent.getType());

      HorizontalPanel horizontalPanel = new HorizontalPanel();
      horizontalPanel.setHeight("100%");
      horizontalPanel.add(selectionPlot);
      horizontalPanel.add(optionsSelector);
      selectionPanel.add(horizontalPanel);

      m_widget.add(selectionPanel, overviewPlot, overviewPlot.getHeight());
      selectionPanel.removeStyleName("gwt-StackLayoutPanelContent");
      overviewPlot.removeStyleName("gwt-StackLayoutPanelHeader");

      if (m_regionsNumber == 1) {
        showOverflow(horizontalPanel);
        overviewPlot.getOverlay().setStyleName(IStyleConstants.EMPTY_DIV_OVERLAY_FOR_CHART_PREVIEW);
      } else {
        overviewPlot.getOverlay().setStyleName(IStyleConstants.SMOKING_OVERLAY_FOR_CHART);
      }

      recountHeight(selectionPlot.getHeight(), overviewPlot.getHeight());
    }

    private void showOverflow(Widget widget) {
      LayoutPanel layoutPanel = (LayoutPanel)widget.getParent().getParent();
      layoutPanel.getWidgetContainerElement(widget.getParent()).setClassName(IStyleConstants.VISIBLE_OVERFLOW);
    }

    private void hideOverflow(Widget widget) {
      LayoutPanel layoutPanel = (LayoutPanel)widget.getParent().getParent();
      layoutPanel.getWidgetContainerElement(widget.getParent()).removeClassName(IStyleConstants.VISIBLE_OVERFLOW);
    }

    private void recountHeight(int plotHeight, int overviewHeight) {
      m_widget.setHeight(Double.toString(m_regionsNumber * overviewHeight + (m_regionsNumber > 0 ? plotHeight + DEFAULT_CONTENT_GAP : 0)));
    }

    private SimplePlot createSelectionPlot(final PassiveSelectionPlot overviewPlot, final OptionsSelector optionsSelector) {
      PlotOptions selectionPlotOptions = createCommonPlotOptions();
      selectionPlotOptions.addYAxisOptions(new AxisOptions().setMinimum(0));
      selectionPlotOptions.setSelectionOptions(new SelectionOptions().setMode(SelectionOptions.SelectionMode.X));
//      selectionPlotOptions.setGridOptions(new GridOptions().setCanvasText(new CanvasTextOptions().setShow(true)));

      final SimplePlot selectionPlot = new SimplePlot(m_plotModel, selectionPlotOptions);
      optionsSelector.setSelectionPlot(selectionPlot);

      selectionPlot.addSelectingListener(new PlotSelectingListener() {
        @Override
        public void onPlotSelecting(PlotSelectionArea area) {
          overviewPlot.setSelection(area);
        }
      });

      selectionPlot.addSelectedListener(new PlotSelectedListener() {
        @Override
        public void onPlotSelected(PlotSelectionArea area) {
          overviewPlot.setSelection(area);
        }
      });

      selectionPlot.addSelectedListener(new PlotSelectedListener() {
        @Override
        public void onPlotSelected(PlotSelectionArea area) {
          refresh(area, optionsSelector);
        }
      });

      selectionPlot.addUnselectedListener(new PlotUnselectedListener() {
        @Override
        public void onPlotUnselected() {
          overviewPlot.clearSelection();
        }
      });

      resizePlot(selectionPlot);

      return selectionPlot;
    }

    private void refresh(PlotSelectionArea area, final OptionsSelector optionsSelector) {
      if (area != null && optionsSelector != null) {
        String metric = optionsSelector.getMetric();
        if (!metric.equals("Add…")) {
          m_chartComparisonService.compare(optionsSelector.getMetric(), m_ids, new Range(area.getX().getFrom(), area.getX().getTo() - area.getX().getFrom()), new AsyncCallback<ComparisonResult>() {
            @Override
            public void onFailure(Throwable caught) {
              m_errorDialog.setMessage(caught);
              m_errorDialog.center();
            }

            @Override
            public void onSuccess(ComparisonResult result) {
              optionsSelector.setResult(result);
            }
          });
        }
      }
    }

    private PassiveSelectionPlot createPassiveSelectionPlot() {
      PlotOptions overviewPlotOptions = createCommonPlotOptions();
      overviewPlotOptions.setSelectionOptions(new SelectionOptions().setMode(SelectionOptions.SelectionMode.X));
      overviewPlotOptions.addXAxisOptions(new AxisOptions().setShow(false));
      PassiveSelectionPlot passiveSelectionPlot = new PassiveSelectionPlot(m_plotModel, overviewPlotOptions);
      resizePreviewPlot(passiveSelectionPlot);
      return passiveSelectionPlot;
    }

    private class RegionSelectorAdder extends Composite {
      public RegionSelectorAdder() {
        HorizontalPanel panel = new HorizontalPanel();
        panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);

        PassiveSelectionPlot fadedPlot = createPassiveSelectionPlot();

        FlowPanel plotOverlay = fadedPlot.getOverlay();
        plotOverlay.addStyleName(IStyleConstants.FADING_OVERLAY_FOR_CHART);

        panel.add(fadedPlot);

        FlowPanel separator = new FlowPanel();
        separator.setWidth("25");
        panel.add(separator);

        addAddButton(panel, plotOverlay);

        initWidget(panel);
      }

      private void addAddButton(HorizontalPanel panel, final FlowPanel plotOverlay) {
        PushButton button = new PushButton(new Image("images/adder.png"), new ClickHandler() {
          @Override
          public void onClick(ClickEvent event) {
            addPlot();
          }
        });

        button.addMouseOverHandler(new MouseOverHandler() {
          @Override
          public void onMouseOver(MouseOverEvent event) {
            plotOverlay.removeStyleName(IStyleConstants.FADING_OVERLAY_FOR_CHART);
          }
        });

        button.addMouseOutHandler(new MouseOutHandler() {
          @Override
          public void onMouseOut(MouseOutEvent event) {
            plotOverlay.addStyleName(IStyleConstants.FADING_OVERLAY_FOR_CHART);
          }
        });

        panel.add(button);
      }
    }

    private class OptionsSelector extends Composite {
      private TabLayoutPanel m_widget = new TabLayoutPanel(1.5, Style.Unit.EM);
      private Set<String> m_comparators;
      private DialogBox m_dialogBox;
      private final ListBox m_listBox = new ListBox();
      private SimplePlot m_selectionPlot;

      public OptionsSelector() {
        m_chartComparisonService.getComparators(new AsyncCallback<Set<String>>() {
          @Override
          public void onFailure(Throwable caught) {
            m_errorDialog.setMessage(caught);
            m_errorDialog.center();
          }

          @Override
          public void onSuccess(Set<String> result) {
            m_comparators = result;
            createDialogBox();
          }
        });

        m_widget.setWidth(Integer.toString(Window.getClientWidth() - DEFAULT_CHART_WIDTH) + "px");
        m_widget.setHeight("100%");

        m_widget.setStyleName(IStyleConstants.CHART_TAB_LAYOUT_PANEL);

        Label plus = new Label("Add…");
        plus.setStyleName(IStyleConstants.POINTED);
        plus.addClickHandler(new ClickHandler() {
          @Override
          public void onClick(ClickEvent event) {
            showMetricsList();
          }
        });

        m_widget.add(new SimplePanel(), plus);

        m_widget.addBeforeSelectionHandler(new BeforeSelectionHandler<Integer>() {
          @Override
          public void onBeforeSelection(BeforeSelectionEvent<Integer> integerBeforeSelectionEvent) {
            if (integerBeforeSelectionEvent.getItem() == m_widget.getWidgetCount() - 1) {
              integerBeforeSelectionEvent.cancel();
            }
          }
        });

        initWidget(m_widget);
      }

      private void showMetricsList() {
        rebuildMetricsList();
        m_dialogBox.center();
        m_dialogBox.show();
      }

      private void rebuildMetricsList() {
        m_listBox.clear();
        for (String comparator : m_comparators) {
          m_listBox.addItem(comparator);
        }
      }

      public String getMetric() {
        return ((Label)m_widget.getTabWidget(m_widget.getSelectedIndex())).getText();
      }

      private void createDialogBox() {
        m_dialogBox = new DialogBox();
        m_dialogBox.setGlassEnabled(true);
        m_dialogBox.setAnimationEnabled(true);

        VerticalPanel dialogContents = new VerticalPanel();
        dialogContents.setSpacing(4);
        m_dialogBox.setWidget(dialogContents);

        final Button addButton = new Button("Add", new ClickHandler() {
          public void onClick(ClickEvent event) {
            m_dialogBox.hide();

            final VerticalPanel content = new VerticalPanel();
            final Label tabLabel = new Label(m_listBox.getItemText(m_listBox.getSelectedIndex()));

            tabLabel.addDomHandler(new ContextMenuHandler() {
              @Override
              public void onContextMenu(final ContextMenuEvent event) {
                event.preventDefault();
                event.stopPropagation();

                final int x = event.getNativeEvent().getClientX();
                final int y = event.getNativeEvent().getClientY();

                final PopupPanel m_rootPopupMenu = new PopupPanel(true);
                final MenuBar m_menuBar = new MenuBar(true);

                m_menuBar.addItem(new MenuItem("Delete", false, new Command() {
                  @Override
                  public void execute() {
                    m_rootPopupMenu.hide();
                    m_comparators.add(tabLabel.getText());
                    m_widget.remove(content);
                  }
                }));

                m_rootPopupMenu.setWidget(m_menuBar);

                m_rootPopupMenu.setModal(true);
                m_rootPopupMenu.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
                  @Override
                  public void setPosition(int offsetWidth, int offsetHeight) {
                    int left = (x + offsetWidth < Window.getClientWidth()) ? x :
                            Window.getClientWidth() - offsetWidth;
                    int top = (y + offsetHeight < Window.getClientHeight()) ? y :
                            Window.getClientHeight() - offsetHeight;
                    m_rootPopupMenu.setPopupPosition(left, top);
                  }
                });
              }
            }, ContextMenuEvent.getType());

            m_widget.insert(content, tabLabel, m_widget.getWidgetCount() - 1);
            m_comparators.remove(m_listBox.getItemText(m_listBox.getSelectedIndex()));

            m_widget.selectTab(m_widget.getWidgetCount() - 2);
            refresh(m_selectionPlot.getSelection(), OptionsSelector.this);
          }
        });

        final Button closeButton = new Button("Cancel", new ClickHandler() {
          public void onClick(ClickEvent event) {
            m_dialogBox.hide();
          }
        });

        dialogContents.add(m_listBox);

        dialogContents.add(new HorizontalPanel() {{add(addButton); add(closeButton); setCellHorizontalAlignment(closeButton, HorizontalAlignmentConstant.endOf(HasDirection.Direction.LTR)); setWidth("100%");}});
      }

      public void setResult(final ComparisonResult result) {
        Widget contentWidget = m_widget.getWidget(m_widget.getSelectedIndex());
        if (contentWidget instanceof VerticalPanel) {
          VerticalPanel content = (VerticalPanel)contentWidget;

          content.clear();

          for (String string : result.getInfo()) {
            content.add(new Label(string));
          }

          if (result.getChartData() != null) {
            SimplePlot simplePlot = createSimplePlot(createPlotModel(new ArrayList<ChartData>() {{add(result.getChartData());}}));
            simplePlot.setHeight(250);
            content.add(simplePlot);
          }
        } else {
          m_errorDialog.setMessage("Content widget is not a vertical panel.");
          m_errorDialog.center();
        }
      }

      public void setSelectionPlot(SimplePlot selectionPlot) {
        m_selectionPlot = selectionPlot;
      }
    }
  }
}
